重载可以分为运算符的重载和函数的重载,但本质上都是一样的。

在 C++ 中,函数可以重载。只要函数中形参的个数或类型不同,则同一个函数名可用于定义不同的函数。编译器将根据函数调用时的实参确定调用哪一个函数。在重载函数集合中选择适合的函数的过程称为函数匹配。

通过操作符重载,可以重定义大多数操作符,使它们用于类类型对象。明智地使用操作符重载可以使类类型的使用像内置类型一样直观。标准库为容器类定义了几个重载操作符。这些容器类定义了下标操作符以访问数据元素,定义了 * 和 -> 对容器迭代器解引用。这些标准库的类型具有相同的操作符,使用它们就像使用内置数组和指针一样。允许程序使用表达式而不是命名函数,可以使编写和阅读程序容易得多。

重载操作符是具有特殊名称的函数:保留字 operator 后接需定义的操作符号。像任意其他函数一样,重载操作符具有返回类型和形参表。

大多数重载操作符可以定义为普通非成员函数或类的成员函数。

操作符其实就是一个函数,操作符有一元操作符、二元操作符、三元操作符,就如同一个函数名加一个参数、或二个相同类型参数、或三个相同类型参数;所以操作会可以重载,用于对象的操作,阅读起来更直观;

可以从容器中检索单个元素的容器类一般会定义下标操作符,即 operator[]。标准库的类 string 和 vector 均是定义了下标操作符

定义下标操作符比较复杂的地方在于,它在用作赋值的左右操作符数时都应该能表现正常。下标操作符出现在左边,必须生成左值,可以指定引用作为返回类型而得到左值。只要下标操作符返回引用,就可用作赋值的任意一方。

运算符重载定义了将运算符用于对象时应执行的操作。

重载:一个类中多个方法相同名字不同参数;

重写:分别属于父类和子类的两个方法同样的名字和参数;

一个集合运算的问题,也就是针对复合数据类型(类也是)而言,结构体允许做为函数的参数和返回值,可以赋值操作,数组也是。对于对象,其集合操作有作为函数的参数或返回值,可以使用拷贝构造函数,对于其它类似普通变量的运算符操作,需要重载操作符。

操作符重载就是让操作符有了多种用法。其实质如同定义成员函数,只是相对于使用成员函数调用,操作符的调用看起来更直观自然。

重载后操作符的各个操作数必须要有一个是自定义数据类型。也就是说,重载操作符是对自定义数据类型操作的重载,如果有函数替换,也就是函数参数中必须有一个是自定义数据类型。

重载运算符,就相当于用运算符来代替函数名。

重载是指对于函数而言,通过函数名相同,而而函数的其它签名(函数名以外)不同;

重写是指在类的继承关系中,在派生类中使用与其类完全相同的方法签名;

重载(overload)和重写(overried)的区别

(1)重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

(2)重写:是指子类重新定义复类虚函数的方法。

从实现原理上来说:

(1)重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。例如,下面的代码有两个同名函数:

function func(p:integer):integer;

function func(p:string):integer;

那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就已经绑定了(早绑定),因此,重载和多态是没有关系的。

(2)重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出???)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

类成员函数的重载、覆盖和隐藏的区别

以下成员函数被重载的特征。

相同的范围(在同一个类中);

函数名字相同;

参数不同;

virtual关键字可有可无。

覆盖是指派生类函数覆盖基类函数,特征如下所示。

不同的范围(分别位于派生类与基类);

函数名字相同;

参数相同;

基类函数必须有virtual关键字。

隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数都将被隐藏(注意别与重载混淆)。

(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意不要与覆盖混淆)

complex add(const complex &c1, const complex &c2)
{
    complex temp(c1.real + c2.real, c1.imag + c2.imag);
    return temp;
}
complex operator+(const complex& c1, const complex& c2)
{
    complex temp(c1.real + c2.real, c1.imag + c2.imag);
    return temp;
}

c1+c2会被编译器理解为:

::operator+(c1,c2)

C++是如何实现函数重载的?

编译器为每个函数重新取一个内部名字。当遇到调用重载函数时,编译器分析实际参数的个数和类型,确定一个形式参数表与实际参数表一致的重载函数,将这个重载函数的内部名字替代函数调用时的函数名。

为什么要使用运算符重载?

运算符重载可以使内置的运算符用于类的对象,使C++的功能得到了扩展。

下标运算符为什么要重载成成员函数?下标运算符重载函数为什么要用引用返回?

下标运算符的第一个运算数是数组名,即当前类的对象。将下标运算符重载成成员函数时,编译器会将程序中诸如a[i]的下标变量的引用改为a.operator[](i)。如果a不是当前类的对象,编译器就会报错。下标变量是左值,所以必须用引用返回

如何禁止同类对象之间的相互赋值?

将赋值运算符重载函数设为类的私有成员函数就可以了。

运算符重载的限制

不是所有的运算符都能重载

重载不能改变运算符的优先级和结合性

重载不能改变运算符的操作数个数

不能创建新的运算符

运算符重载就是写一个函数解释某个运算符在某个类中的含义

要使得系统能自动找到重载的这个函数,函数名必须要体现出和某个被重载的运算符的联系。

C++中规定,重载函数名为

operator@

其中,@为要重载的运算符。如要重载“+”运算符,该重载函数名为operator+。要重载赋值运算符,函数名为operator=。

运算符的重载不能改变运算符的运算对象数。因此,重载函数的形式参数个数(包括成员函数的隐式指针this)与运算符的运算对象数相同

运算符重载可以重载成成员函数也可以重载成全局函数实现。重载成全局函数时,最好把此函数设为友员函数

当把一个一元运算符重载成成员函数时,该函数没有形式参数。

把一个二元运算符重载成成员函数时,该函数只有一个形式参数,就是右操作数,当前对象是左操作数。

大多数运算符都可以重载成成员函数或全局函数。

赋值(=)、下标([])函数调用(())和成员访问(->)必须重载成成员函数。

具有赋值意义的运算符,如复合的赋值运算符以及++和--,不一定非要定义为成员函数,但最好定义为成员函数。

具有两个运算对象的运算符最好重载为全局函数,这样可以使得应用更加灵活。如果把加运算定义成全局函数,r是有理数类的对象,则2+r是一个合法的表达式。

对任一类,如果用户没有自定义赋值运算符函数,那么系统为其生成一个缺省的赋值运算符函数,在对应的数据成员间赋值。

一般情况下,这个缺省的赋值运算符重载函数能满足用户的需求。但是,当类含有类型为指针的数据成员时,可能会带来一些麻烦。

在赋值运算符重载函数中,已经将参数的值赋值给了当前对象,那为什么还需要返回值呢?记住,在C++中,赋值是一个运算,它可以形成一个表达式,而该表达式的结果值就是赋给左边的对象的值。因此,赋值运算符重载函数必须返回赋给左边的对象值。

重载函数是通过“名字压延”方法来实现。即在编译时将函数名和参数结合起来创造一个新的函数名,用新的名字替换原有名字。

编译器从模板生成一个特定的类或函数的过程称为模板的实例化。

一般情况下,重载的运算符函数(operator<<)是类的一个友元,而不是类的成员函数。

可重载操作符+(以及其他许多操作符)来接受类类型的实参。重载操作符+与定义函数add的区别只涉及语法上的一处微小变动。对于重载的操作符+,它的定义与函数add 的定义基本相同。唯一区别是要使用名称+,而不是名称add。另外,要在操作符+之前附加关键字operator。

包括+,-,/,%等在内的(二元)操作符本质上是函数。调用这种“函数”时,要以不同的语法列出实参。操作符函数的实参要在操作符前后列出;普通函数的实参则在函数名之后的圆括号中列出。操作符函数的定义与普通函数相似,只是要在操作符之前附加保留字operator。可为类类型重载预定义操作符(比如+),为这些操作符赋予新含义。虽然并非必须,但操作符可作为类的友元。

操作符重载规则

重载操作符时,至少一个实参必须是类类型。

重载的操作符可以是(但不一定是)类的友元;操作符函数可以是类的成员,也可以是普通(非友元)函数()。

不能新建操作符。只能重载现有操作符,比如+,-,*,/,%等。

不能改变操作符获取的实参数量。例如,重载%时,不能把它从二元操作符变成一元操作符;重载++时,不能把它从一元操作符变成二元操作符。

不能改变操作符的优先级。重载的操作符具有和原始版本一样的优先级。例如,x * y + z 总是表示(x * y) + z,即使x,y 和z 是对象,而且操作符+和*已针对相应的类进行了重载。

以下操作符不可重载:圆点操作符(.)、作用域解析操作符(::)以及本书没有讨论的操作符.*和?:。

虽然赋值操作符=能重载,将它的默认含义转变成其他含义,但方式不同于本节描述的方式。

如果类定义包含了恰当的构造函数,系统会自动执行特定的类型转换。例如,如果程序包含给出的 Money类定义,可以在程序中使用下面这些语句:

Money baseAmount(100, 60), fullAmount;

fullAmount = baseAmount + 25;

fullAmount.output(cout);

输出结果如下:

125.60

代码看起来简单和自然,但存在一个容易被忽视的问题。在表达式baseAmount+25中,25的类型不合适。如果只重载了操作符+,使其可用于Money类型的两个值。但没有重载操作符+,使其获取一个Money类型的值和一个整数作为参数。常量25是整数,不是Money类型。常量25可以是int或long,但不能作为Money值使用,除非类定义告诉系统如何将整数转换成Money值。为了让系统知道25的意思是$25.00,唯一的办法就是包括一个构造函数,它获取long类型的一个参数。一旦系统看到以下表达式:

baseAmount + 25

首先就会检查操作符+是否已针对Money类型的值和整数值的组合进行了重载。由于没有进行这种重载,所以系统接着检查是否有一个构造函数获取单个整数值参数。发现这样的构造函数,就用那个构造函数将整数25转换成Money类型的值。获取单个long值作为参数的构造函数告诉系统如何将整数(比如25)转换成Money值。单参数构造函数指出25应该转换成Money类型的一个对象,该对象的成员变量allCents等于2500;换言之,构造函数将25转换成Money类型的对象,该对象代表$25.00。注意,除非有合适的构造函数,否则类型转换不会成功。例如,Money类没有能获取单个double值的构造函数,所以假如将baseAmount和fullAmount声明为Money类型的对象,以下语句是非法的,将产生一条错误消息:

fullAmount = baseAmount + 25.67;

为了使上述语句合法,可更改Money 类的定义添加另一个构造函数。需添加的构造函数的声明如 下所示:

class Money
{
    public:
    . . .
    Money(double amount);
    // 初始化对象,让它的值表示$amount
    . . .

操作符(比如+和==)可以重载,使它们能应用于你定义的类类型的对象。

重载操作符>>或<<时,返回的类型应该是一个流类型,而且返回的类型必须是引用(为返回类型名称附加后缀&)。

2

重载。重载是指允许同时存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

重写。重写是指子类重新定义父类虚函数的方法。

从实现原理上来说,两者的区别如下。

重载。重载编译器根据函数声明中的不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。例如,有两个同名函数--void func(int n)和void func(string str),虽然这两个函数的名称都是func,但是经过编译器修饰后的函数名称可能是这样的:int_func和str_func。对于这两个函数的调用,在编译期间就已经确定了,它们是諍态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关。

重写。重写和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据賦给它的不同子类指针动态地调用属于子类的该函数,这样的函数调用在编译期是无法确定的(无法给出调用子类的虚函数的地址)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

函数重写:在类的继承关系中,基类定义为virtual函数;

函数隐藏:在类的继承关系中,基类没有定义为virtual函数;

在C++中,系统提供的字符串处理能力比较弱,都是通过字符处理函数来实现的,并且不能直接对字符串进行加法、减法,字符串的拼接,字符串之间的相互赋值等操作。可以通过应用C++提供的运算符重载机制,可以提供字符串的直接操作能力,使得字符串的操作与一般的数据一样方便。

为什么需要函数重载?

1 用相同的函数名来定义一组功能相同或类似的函数,程序的可读性增强;

2 这样做减少了函数名的数量,避免了名字空间的污染,而且减少对用户的复杂性;

3 简化代码,有效提高代码的可重用性,且体现了面向对象编程的优越性;

4 构造函数都与类名相同,如果没有函数重载,要想实例化不同对象将十分困难;

5 操作符重载,本质上也是函数重载,极大丰富了已有操作符的含义,方便使用。

friend Sample operator+( Sample &s1, Sample &s2);//二元运算符,两个参数则是非成员函数,一个参数则是成员函数

Sample operator+( Sample &s); //二元运算符,两个参数则是非成员函数,一个参数则是成员函数

要实现函数重载,需要使用不同形参列表为同一函数编写不同的定义。

一般情况下,重载输入(输出)运算符函数不能是类的成员函数。因为如果一个运算符函数是类的成员,则其左运算数就应当是调用运算符函数的类的对象,而此点是无法改变的。

重载输入(输出)运算符时,其左边的参数是流,而右边参数是类的对象。因此重载输入(输出)运算符函数必须是非成员函数-而用友元函数。

运算符函数重载:将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。

当一元运算符的操作数,或者二元运算符的左操作数是类的一个对象时,以成员函数重载;当一个运算符的操作需要修改类对象状态时,应该以成员函数重载。如果以成友元函数重载,则使用引用参数修改对象。

当运算符的操作数(尤其是第一个操作数)希望有隐式转换,则重载算符时必须用友元函数。

Note that there’s no confusion between the negative operator- and the minus operator- since they have a different number of parameters.

Don’t define overloaded operators that don’t make sense for your class.

Operator() is also commonly overloaded to implement functors (or function object), which are classes that operate like functions. The advantage of a functor over a normal function is that functors can store data in member variables (since they are classes).

Typically, we won’t be able to use a member overload if the left operand is either not a class (e.g. int), or it is a class that we can’t modify (e.g. std::ostream).

重载运算符的规则如下:

① c++不允许用户自己定义新的运算符,只能对已有的c++运算符进行重载;

② c++不能重载的运算符只有5个;

③ 重载不能改变运算符运算对象的个数;

④ 重载不能改变运算符的优先级和结合性;

⑤ 重载运算符的函数不能有默认的参数;

⑥ 重载的运算符必须和用户定义的自定义类型的对象一起使用,至少应有一个是类对象,即不允许参数全部是C++的标准类型。

运算符重载的一些规则:

① 一般情况下,单目运算符最好重载为类的成员函数,双目运算符则最好重载为类的友元函数;

② 双目运算符=、()、[]、->不能重载为类的友元函数;

③ 类型转换函数只能定义为一个类的成员函数,而不能定义为类的友元函数;

④ 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好;

⑤ 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数;

⑥ 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用);如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数必须作为一个友元函数来实现。

template
T triangleArea (T base, T height)
{
	return base * height * .5;
}
triangleArea(.5, .5); //模板的参数应该是一个类型而不是一个值

代码出现T的任何地方,它都会被double代替。就像是一个这样的数据类型替代声明。

在有些情况下,编译器也可以根据函数的参数来推断模板参数的值:

如果是模板类,则该类的所有函数(不管有没用到模版参数)的实现都必须放在头文件中。